class Fmt{
    static _fmt_token_key = "_fmt_token_value";
    /**
     * 缓存配置 session：sessionStorage local：localStorage
     * @type {string}
     */
    static cache_config = "session";
    constructor() {
    }
    /**
     * @returns {string}
     */
    static ctx(){
        const pathName = window.location.pathname.substring(1);
        const webName = pathName === '' ? '' : pathName.substring(0, pathName.indexOf('/'));
        if(!webName){
            return window.location.protocol + '//' + window.location.host;
        }
        return window.location.protocol + '//' + window.location.host + '/' + webName;
    };
    /**
     * 缓存封装
     * @type {{
     *      set: function(String, *): void,
     *      get: function(String): string,
     *      clear: function(): void,
     *      remove: function(String): void,
     *      key: function(Number): string
     * }}
     */
    static cache = {
        /**
         * 设置缓存
         * @param {String} key
         * @param value
         */
        set: (key, value) => {
            const config = Fmt.cache_config;
            const storage = config === 'session' ?
                window.sessionStorage : window.localStorage;
            storage.setItem(key, value);
        },
        /**
         * 获取缓存
         * @param {String} key
         * @returns {string}
         */
        get: (key) => {
            const config = Fmt.cache_config;
            const storage = config === 'session' ?
                window.sessionStorage : window.localStorage;
            return storage.getItem(key);
        },
        /**
         * 清除缓存
         * @param {String} key
         */
        remove: (key) => {
            const config = Fmt.cache_config;
            const storage = config === 'session' ?
                window.sessionStorage : window.localStorage;
            storage.removeItem(key);
        },
        /**
         * 获取缓存key
         * @param {Number} num
         * @returns {string}
         */
        key: (num) => {
            const config = Fmt.cache_config;
            const storage = config === 'session' ?
                window.sessionStorage : window.localStorage;
            return storage.key(num);
        },
        /**
         * 此方法会清空整个浏览器的LocalStorage缓存
         */
        clear: () => {
            const config = Fmt.cache_config;
            const storage = config === 'session' ?
                window.sessionStorage : window.localStorage;
            storage.clear();
        }
    };
    /**
     * 常用工具方法封装
     * @type {{isEmpty: function(*): boolean, typeOf: function(*): string}}
     */
    static utils = {
        /**
         * 获得对象真正的类型
         * 可检测出Array,Object,Function,String,Number,Boolean,Date,Null,Undefined,HTMLDivElement(Dom节点都包含HTML)等
         * @param obj
         * @returns {string}
         */
        typeOf: (obj) => Object.prototype.toString.call(obj).slice(8, -1),
        /**
         * 定义判断为空函数，可判断function(){}，0，''，[]，{}，undefined，null
         * @param data
         * @returns {boolean}
         */
        isEmpty: (data) => {
            let type = this.utils.typeOf(data);
            if (!data) {
                return true;
            } else if (type === 'Function') {
                return (data.toString().replace(/\s+/g, '').match(/{.*}/g)[0] === '{}');
            } else if (type === 'Array') {
                return data.join('') === '';
            } else if (type === 'Object') {
                return (Object.keys(data).length === 0)
            } else {
                return false;
            }
        },
        /**
         * 该函数是深度拷贝函数，返回一个一模一样的对象
         * @param data
         * @returns {*|Date|{}}
         */
        clone: (data) => {
            let cloneObj = (obj) => {
                if (obj === null) {
                    return null;
                } else if (typeof obj !== 'object') {
                    return obj;
                } else if (obj.constructor === Date) {
                    return new Date(obj);
                } else {
                    let newObj = {};
                    for (let k in obj) {
                        if (obj[k] == null) {
                            newObj[k] = null;
                        } else if ((typeof obj[k]) == 'object' && !obj[k].nodeType) {
                            newObj[k] = axClone(obj[k]);
                            if (obj[k] instanceof Array) {
                                let newArray = [];
                                for (let i of obj[k]) {
                                    newArray.push(axClone(i));
                                }
                                newObj[k] = newArray;
                            }
                        } else {
                            newObj[k] = obj[k];
                        }
                    }
                    return newObj;
                }
            }
            if (Array.isArray(data)) {
                return data.map(k => cloneObj(k));
            } else {
                return cloneObj(data);
            }
        },
        /**
         * 扩展，source相同属性的值去覆盖target, target如果没有这个属性就新增
         * @param {Object} target
         * @param {Object} source
         * @returns {Object}
         */
        extend: (target, source) => {
            if(this.utils.typeOf(target) !== 'Object') return null;
            if(this.utils.typeOf(source) !== 'Object') return null;
            for (const key in source) {
                // 使用for in会遍历数组所有的可枚举属性，包括原型。
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    target[key] = source[key];
                }
            }
            return target;
        },
        /**
         * 判断选择器类型
         * @param str
         * @returns {string|boolean}
         */
        strType: (str) => {
            if (typeof str !== 'string') {
                console.error('Argument must be a string!')
                return false;
            }
            str = str.trim();
            let isUpperCase = (letter) => {
                    if (letter >= 'A' && letter <= 'Z') {
                        return true;
                    }
                },
                type = '';
            if (str) {
                if (str.includes(' ')) {
                    type = 'level';
                } else {
                    if (str.startsWith('#')) {
                        type = 'id';
                    } else if (str.startsWith('.')) {
                        type = 'class';
                    } else if (str.startsWith('[') && str.endsWith(']')) {
                        type = 'name';
                    } else if ([...str].every(i => isUpperCase(i))) {
                        type = 'node';
                    }
                }
            }
            return type;
        },
        /**
         * 将#id转成DOM
         * @param elem
         * @returns {*|boolean}
         */
        idToDom: (elem) => {
            let type = this.utils.typeOf(elem);
            if (!elem) {
                console.warn('Node selector is empty!');
                return false;
            } else if (type === 'String') {
                let trim = elem.trim();
                if (this.utils.strType(trim)) {
                    if (this.utils.strType(trim) === 'name' && !trim.includes('=')) {
                        trim = trim.slice(0, 1) + 'name=' + trim.slice(1);
                    }
                    return document.querySelector(trim);
                } else {
                    console.warn('No node is found with this string!');
                    return false;
                }
            } else if (type.includes('HTML')) {
                return elem;
            } else {
                return false;
            }
        },
        /**
         * 从表单控件取值，用于校验判断，适合的控件：checkbox、radio、select-multiple和file
         * @param name
         * @param type
         * @param format
         * @param form
         * @param separator
         * @returns {*[]|boolean}
         */
        multiValues: (name, type = 'checkbox', format = 'array', form, separator = ',') => {
            let inputs,
                values = [],
                parent = form ? this.utils.idToDom(form) : '';
            if (this.utils.strType(name) === 'NodeList' || this.utils.strType(name) === 'Array') {
                if (type.includes('select') || type === 'file') {
                    console.warn('Can not be an node array in this type!');
                    return false;
                }
                inputs = name;
            } else if (typeof name == 'object' || (this.utils.strType(name) && this.utils.strType(name) !== 'name')) {
                let dom = this.utils.idToDom(name);
                if (type.includes('select') || name.nodeName === 'SELECT') {
                    type = 'select';
                    inputs = dom.options;
                } else if (type === 'file' || dom.type === 'file') {
                    type = 'file';
                    inputs = [dom];
                } else {
                    inputs = [...dom.querySelectorAll('input')];
                }
            } else {
                let selector = '';
                if (this.utils.strType(name) === 'name') {
                    selector = name.replace('[', '[name=');
                } else {
                    selector = `[name=${name}]`;
                }
                if (type.includes('select')) {
                    inputs = parent ? parent.querySelector(selector).options : document.querySelector(selector).options;
                } else if (type === 'file') {
                    inputs = parent ? [parent.querySelector(selector)] : [document.querySelector(selector)];
                } else {
                    inputs = parent ? [...parent.querySelectorAll(selector)] : [...document.querySelectorAll(selector)];
                }
            }
            for (let i = 0; i < inputs.length; i++) {
                let condition,
                    item = inputs[i];
                if (type.includes('select')) {
                    condition = item.selected;
                } else {
                    condition = item.checked;
                }
                if (type === 'file') {
                    values = [...item.files];
                } else {
                    if (condition) {
                        values.push(item.value);
                    }
                }
            }
            if (format === 'string') {
                values = values.join(separator)
            }
            return values;
        },
        /**
         * 表单序列化，支持将表单值序列化为以“&”连接的字符串，和对象数组[{name:'',value:''},...]
         * @param element
         * @param type
         * @param separator
         * @returns {{}}
         */
        serialize: (element, type = "string", separator = ',') => {
            let inputs,
                items = [],
                output;
            if (Array.isArray(element)) {
                inputs = element;
            } else {
                inputs = [...this.utils.idToDom(element).querySelectorAll('[name]')].filter(i => ['INPUT', 'SELECT', 'TEXTAREA'].includes(i.nodeName) && i.name && !i.name.includes('_ax_alt'));
            }
            inputs.forEach(i => {
                let have = items.find(k => k.name === i.name);
                if (have) {
                    if (i.type === 'checkbox' || i.type === 'radio') {
                        have.dom.push(i);
                    }
                } else {
                    items.push({ name: i.name, type: i.type, dom: [i] })
                }
            });
            items.forEach(i => {
                if (i.type === 'file') {
                    i.value = this.utils.multiValues(i.dom[0], 'file', 'array');
                } else if (i.type === 'checkbox') {
                    i.value = this.utils.multiValues(i.dom, 'checkbox', 'string', '', separator);
                } else if (i.type === 'radio') {
                    i.value = this.utils.multiValues(i.dom, 'radio', 'string');
                } else if (i.type.includes('select')) {
                    i.value = this.utils.multiValues(i.dom[0], 'select', 'string', '', separator);
                } else {
                    i.value = i.dom[0].value;
                }
            });
            if (type === 'string') {
                let str = '';
                items.forEach(k => {
                    str += '&' + k.name + '=' + k.value;
                });
                output = str;
            } else if (type === 'json') {
                let obj = {}
                items.forEach(k => {
                    obj[k.name] = k.value;
                });
                output = obj;
            } else if (type === 'array') {
                items.forEach(k => {
                    delete k.type;
                    delete k.dom;
                })
                output = items;
            }
            return output;
        }
    };
    /**
     * XMLHttpRequest封装
     * @param {{url, data, method, responseType, contentType, timeout, headers}} options
     * @returns {Promise<unknown>}
     */
    #axiosPrivate(options){
        let data = options.data;
        let url = options.url;
        let method = options.method || 'GET';
        let responseType = options.responseType || 'json';
        let contentType = options.contentType || 'application/x-www-form-urlencoded';
        let timeout = options.timeout || 0;
        /**
         * 序列化参数
         * @param data
         * @returns {string}
         */
        let serialize = (data) => {
            if (Fmt.utils.isEmpty(data)) return '';
            let pairs = [];
            for (let name in data) {
                if (!data.hasOwnProperty(name)) continue;
                if (Fmt.utils.typeOf(data[name]) === 'Function') continue;
                let value = data[name].toString();
                name = encodeURIComponent(name);
                value = encodeURIComponent(value);
                pairs.push(name + '=' + value);
            }
            return pairs.join('&');
        }
        /**
         * 创建xhr请求对象
         * @returns {XMLHttpRequest || ActiveXObject}
         */
        let xhrObj = () => {
            let xhr = '';
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            }else {
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
            }
            return xhr;
        }
        return new Promise((resolve, reject) => { // promise封装
            let xhr = xhrObj();
            let params = null;
            xhr.onreadystatechange = handler;
            if (method.toUpperCase() === 'GET') {
                url = url + '?now=' + new Date().getTime() + '&' + serialize(data);
            } else if (method.toUpperCase() === 'POST') {
                params = serialize(data);
            }
            xhr.responseType = responseType;
            xhr.timeout = timeout;
            xhr.open(method, url);
            xhr.setRequestHeader('Content-Type', contentType);
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.setRequestHeader("Access-Control-Allow-Origin","*");
            if(!Fmt.utils.isEmpty(options.headers) && Fmt.utils.typeOf(options.headers) === 'Object'){
                for (let key in options.headers) {
                    xhr.setRequestHeader(key, options.headers[key]);
                }
            }
            xhr.send(params);

            function handler() {
                if (this.readyState !== 4) {
                    return;
                }
                if (this.status === 200) {
                    resolve(this.response);//正确回调
                } else {
                    reject(new Error(this.statusText));//错误回调
                }
            }
        })
    }
    /**
     * XMLHttpRequest封装
     * <pre>
     *     使用方式：
     *     const options = {
     *         url: Fmt.ctx() + '/salt',
     *         data: {username: username},
     *         method: 'post'
     *     };
     *     Fmt.axios(options).then((res) => {
     *         console.warn(res)
     *     }).catch((err) => {console.warn(err)});
     * </pre>
     * @param {{url, data, method, responseType, contentType, timeout, headers}} options
     * @returns {Promise<unknown>}
     */
    static axios(options){
        let fmt = new Fmt();
        return fmt.#axiosPrivate(options);
    }
}